﻿using Microsoft.SharePoint;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SPAutomaticListsPopulator.Helprers
{
    public class BatchOperation
    {
        public SPList List { get; set; }
        public SPWeb Web { get; set; }
        public int BatchSize { get; set; }

        private string ListId;

        public Boolean DisableEventReceivers { get; set; }
        public Boolean SystemUpdate { get; set; }

        public List<BatchItem> Items = new List<BatchItem>();

        #region constructors
        public BatchOperation(SPList list)
        {
            List = list;
            Web = list.ParentWeb;
            ListId = list.ID.ToString();
            BatchSize = 5000;
            DisableEventReceivers = false;
        }


        public BatchOperation(SPList list, int batchSize, Boolean disableEventReceivers)
        {
            List = list;
            Web = list.ParentWeb;
            ListId = list.ID.ToString();
            BatchSize = batchSize;
            DisableEventReceivers = disableEventReceivers;
        }

        public BatchOperation(SPList list, int batchSize, Boolean disableEventReceivers, Boolean systemUpdate)
        {
            List = list;
            Web = list.ParentWeb;
            ListId = list.ID.ToString();
            BatchSize = batchSize;
            DisableEventReceivers = disableEventReceivers;
            SystemUpdate = systemUpdate;
        }

        #endregion

        #region Constants
        /// <summary>Methods 
        /// </summary>
        private const string BATCH_FORMAT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><ows:Batch OnError=\"Return\">{0}</ows:Batch>";

        /// <summary> FIeld Name, Field Value 
        /// </summary>
        private const string FIELD_VALUE_FORMAT = "<SetVar Name=\"urn:schemas-microsoft-com:office:office#{0}\">{1}</SetVar>";

        /// <summary>Unique Method Name,  List GUID, FieldValues 
        /// </summary>
        private const string NEW_ITEM_METHOD_FORMAT = "<Method ID=\"New_{0}\">" +
                                            "<SetList>{1}</SetList>" +
                                            "<SetVar Name=\"ID\">New</SetVar>" +
                                            "<SetVar Name=\"Cmd\">Save</SetVar>" +
                                            "{2}" +
                                            "</Method>";

        /// <summary> ItemID, List GUID, FieldValues 
        /// </summary>
        private const string UPDATE_ITEM_METHOD_FORMAT = "<Method ID=\"Update_{0}\">" +
                                            "<SetList>{1}</SetList>" +
                                            "<SetVar Name=\"ID\">{0}</SetVar> " +
                                            "<SetVar Name=\"Cmd\">Save</SetVar>" +
                                            "{2}" +
                                            "</Method>";

        /// <summary>List GUID, ItemID 
        /// </summary>
        private const string DELETE_ITEM_METHOD_FORMAT = "<Method ID=\"Delete_{0}\">" +
                                                         "<SetList Scope=\"Request\">{1}</SetList>" +
                                                         "<SetVar Name=\"Cmd\">Delete</SetVar>" +
                                                         "<SetVar Name=\"ID\">{0}</SetVar> " +
                                                         "</Method>";

        #endregion

        private static void SetFieldValue(StringBuilder fieldValuesBuilder, string fieldName, string fieldValue)
        {
            fieldValuesBuilder.AppendFormat(FIELD_VALUE_FORMAT, fieldName, fieldValue);
        }

        private static void AddDeleteMethod(StringBuilder methodsBuilder, string listID, int itemID)
        {
            methodsBuilder.AppendFormat(DELETE_ITEM_METHOD_FORMAT, itemID, listID);
        }

        private static void AddCreateMethod(StringBuilder methodsBuilder, string listID, int uniqueMethodID, string fieldValues)
        {
            methodsBuilder.AppendFormat(NEW_ITEM_METHOD_FORMAT, uniqueMethodID, listID, fieldValues);
        }

        private static void AddUpdateMethod(StringBuilder methodsBuilder, string listID, int itemID, string fieldValues)
        {
            methodsBuilder.AppendFormat(UPDATE_ITEM_METHOD_FORMAT, itemID, listID, fieldValues);
        }

        public void DeleteItems()
        {
            int itemsCount = Items.Count;
            int firstIdInBatch = 0;

            while (itemsCount > 0)
            {
                if (BatchSize > itemsCount)
                {
                    BatchSize = itemsCount;
                }

                StringBuilder methods = new StringBuilder();
                for (int i = firstIdInBatch; i < firstIdInBatch + BatchSize; i++)
                {
                    AddDeleteMethod(methods, ListId, Items[i].ItemID);
                }

                string batch = string.Format(BATCH_FORMAT, methods);
                if (DisableEventReceivers)
                {
                    using (var disabler = new DisabledEventFiringScope())
                    {
                        Web.ProcessBatchData(batch);
                    }
                }
                else
                {
                    Web.ProcessBatchData(batch);
                }

                firstIdInBatch = firstIdInBatch + BatchSize;
                itemsCount = itemsCount - BatchSize;
            }
        }

        public void UpdateItems()
        {
            int itemsCount = Items.Count;
            int firstIdInBatch = 0;

            while (itemsCount > 0)
            {
                if (BatchSize > itemsCount)
                {
                    BatchSize = itemsCount;
                }

                StringBuilder methods = new StringBuilder();
                for (int i = firstIdInBatch; i < firstIdInBatch + BatchSize; i++)
                {
                    StringBuilder fieldValues = new StringBuilder();

                    foreach (KeyValuePair<string, string> fieldValue in Items[i].fieldValues)
                    {
                        SetFieldValue(fieldValues, fieldValue.Key, fieldValue.Value);
                    }

                    if (SystemUpdate)
                    {
                        if (Items[i].ModifiedDate == default(DateTime))
                        {
                            throw new Exception("Вы не заполнили значение свойства ModifiedDate в экземпляре BatchItem. Используйте конструктор, который принимает SPListItem");
                        }

                        SetFieldValue(fieldValues, "Modified", Items[i].ModifiedDate.ToString("yyyy-MM-ddTHH:mm:ssZ"));
                    }

                    AddUpdateMethod(methods, ListId, Items[i].ItemID, fieldValues.ToString());
                }

                string batch = string.Format(BATCH_FORMAT, methods);
                if (DisableEventReceivers)
                {
                    using (var disabler = new DisabledEventFiringScope())
                    {
                        Web.ProcessBatchData(batch);
                    }
                }
                else
                {
                    Web.ProcessBatchData(batch);
                }

                firstIdInBatch = firstIdInBatch + BatchSize;
                itemsCount = itemsCount - BatchSize;
            }
        }

        public string CreateItems()
        {
            int itemsCount = Items.Count;
            int firstIdInBatch = 0;
            string result = string.Empty;

            while (itemsCount > 0)
            {
                if (BatchSize > itemsCount)
                {
                    BatchSize = itemsCount;
                }

                StringBuilder methods = new StringBuilder();
                for (int i = firstIdInBatch; i < firstIdInBatch + BatchSize; i++)
                {
                    StringBuilder fieldValues = new StringBuilder();

                    foreach (KeyValuePair<string, string> fieldValue in Items[i].fieldValues)
                    {
                        if (!string.IsNullOrEmpty(fieldValue.Value))
                        {
                           // SetFieldValue(fieldValues, fieldValue.Key, SecurityElement.Escape(fieldValue.Value));
                        }
                    }

                    AddCreateMethod(methods, ListId, i, fieldValues.ToString());
                }

                string batch = string.Format(BATCH_FORMAT, methods);
                if (DisableEventReceivers)
                {
                    using (var disabler = new DisabledEventFiringScope())
                    {
                        result = Web.ProcessBatchData(batch);
                    }
                }
                else
                {
                    result = Web.ProcessBatchData(batch);
                }

                firstIdInBatch = firstIdInBatch + BatchSize;
                itemsCount = itemsCount - BatchSize;
            }
            return result;
        }
    }

    public class BatchItem
    {
        public BatchItem(int itemID)
        {
            ItemID = itemID;
        }

        public BatchItem(SPListItem item)
        {
            ItemID = item.ID;
            ModifiedDate = (DateTime)item[SPBuiltInFieldId.Modified];
        }

        public BatchItem()
        {

        }

        public int ItemID { get; set; }
        public Dictionary<string, string> fieldValues = new Dictionary<string, string>();
        public DateTime ModifiedDate { get; set; }

    }

    class DisabledEventFiringScope : SPEventReceiverBase, IDisposable
    {
        public DisabledEventFiringScope()
        {
            EventFiringEnabled = false;
        }

        public void Dispose()
        {
            EventFiringEnabled = true;
        }
    }
}
